package cn.bonoon.kernel.web.html.grid;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import cn.bonoon.handler.impl.StandardAutoManager;
import cn.bonoon.kernel.IdGenerator;
import cn.bonoon.kernel.annotations.OptionArray;
import cn.bonoon.kernel.annotations.QueryExpression;
import cn.bonoon.kernel.annotations.QueryExpressions;
import cn.bonoon.kernel.annotations.ResetProperty;
import cn.bonoon.kernel.descriptors.DescriptorFactory;
import cn.bonoon.kernel.descriptors.ObjectDescriptor;
import cn.bonoon.kernel.descriptors.QueryDescriptor;
import cn.bonoon.kernel.descriptors.QueryPropertyAccessor;
import cn.bonoon.kernel.expression.QueryParameter;
import cn.bonoon.kernel.expression.StandardExpressionParser;
import cn.bonoon.kernel.expression.results.RequestExpressionValue;
import cn.bonoon.kernel.query.PageCondition;
import cn.bonoon.kernel.query.SearchCondition;
import cn.bonoon.kernel.support.entities.Persistable;
import cn.bonoon.kernel.support.services.SearchService;
import cn.bonoon.kernel.util.StringHelper;
import cn.bonoon.kernel.web.SecondaryWorkspace;
import cn.bonoon.kernel.web.annotations.AjaxTreeLoader;
import cn.bonoon.kernel.web.annotations.AutoDataLoader;
import cn.bonoon.kernel.web.annotations.components.EnumSelector;
import cn.bonoon.kernel.web.annotations.condition.ConditionContent;
import cn.bonoon.kernel.web.annotations.condition.ConditionStyle;
import cn.bonoon.kernel.web.annotations.condition.ConditionTree;
import cn.bonoon.kernel.web.annotations.condition.ConditionUI;
import cn.bonoon.kernel.web.annotations.grid.AsDataGrid;
import cn.bonoon.kernel.web.annotations.grid.DetailView;
import cn.bonoon.kernel.web.annotations.grid.GroupView;
import cn.bonoon.kernel.web.annotations.grid.PaginationType;
import cn.bonoon.kernel.web.handlers.ExecutableHandler;
import cn.bonoon.kernel.web.handlers.loader.ComboAutoLoaderHandler;
import cn.bonoon.kernel.web.handlers.loader.ComboBoxAutoLoaderHandler;
import cn.bonoon.kernel.web.handlers.loader.ComboGridAutoLoaderHandler;
import cn.bonoon.kernel.web.handlers.loader.ComboTreeAjaxAutoLoaderHandler;
import cn.bonoon.kernel.web.handlers.loader.ComboTreeAutoLoaderHandler;
import cn.bonoon.kernel.web.handlers.loader.ItemLoaderHandler;
import cn.bonoon.kernel.web.html.AbstractHtmlBuilder;
import cn.bonoon.kernel.web.html.HtmlBuilder;
import cn.bonoon.kernel.web.html.InternalHtmlInfo;

/**
 * <pre>
 * 查询条件的生成依赖于grid列表，当解释查询条件时，必须同时解释一个grid列表
 * 
 * 解释后两个自动合并成一个界面
 * </pre>
 * 
 * <font color='red'>需要再进行优化处理......</font>
 * @author jackson
 *
 */
public class DatagridBuilder extends StandardGridBuilder implements HtmlBuilder, SecondaryWorkspace{
	
	private ConditionStyle conditionObject;
	private static final int DEFAULT_COLUMN_SIZE = 3;

	/**
	 * <pre>
	 * 点击查询按钮时，需要进行查询的条件 ；
	 * 直接从查询条件对象解析出来的参数，传到控制器后会自动对查询对象进行赋值
	 * </pre>
	 */
	private final List<QueryParameter> searchParameters = new ArrayList<QueryParameter>();
	
	private final static int LINE_HEIGHT = 28;
	private final static int LINE_PADDING = 10;
	private final WestBuilder westBuilder;

	private String name;
	
	public DatagridBuilder(ObjectDescriptor descriptor, StandardAutoManager initializer)throws Exception {
		super("bupgrid", null, descriptor, initializer);
		this.westBuilder = new WestBuilder(html, searchParameters);
		__init();
	}
	
	public DatagridBuilder(ObjectDescriptor descriptor, StandardAutoManager initializer, InternalHtmlInfo html) throws Exception{
		super("bupgrid", null, descriptor, initializer, html);
		this.westBuilder = new WestBuilder(html, searchParameters);
		__init();
	}
	
	public DatagridBuilder(AsDataGrid datagrid, ObjectDescriptor descriptor, StandardAutoManager initializer)throws Exception {
		super("bupgrid", datagrid.value(), descriptor, initializer);
		this.westBuilder = new WestBuilder(html, searchParameters);
		__init(datagrid);
	}
	
	public DatagridBuilder(AsDataGrid datagrid, ObjectDescriptor descriptor, StandardAutoManager initializer, InternalHtmlInfo html) throws Exception{
		super("bupgrid", datagrid.value(), descriptor, initializer, html);
		this.westBuilder = new WestBuilder(html, searchParameters);
		__init(datagrid);
	}
	
	private PaginationType pagination;
	private int pageSize;
	
	private void __init(AsDataGrid datagrid){
		pagination = datagrid.pagination();
		pageSize = datagrid.pageSize();
		conditionClass = datagrid.condition();
		if(conditionClass.equals(Object.class)){
			conditionClass = null;
		}
		idField = datagrid.idField();
		if(pagination != PaginationType.NONE && pageSize <= 0){
			//需要使用默认值
			if(pagination == PaginationType.PAGINATION){
				pageSize = 20;
			}else if(pagination == PaginationType.SCROLLVIEW){
				pageSize = 50;
			}
		}
	}
	
	private void __init(){
		pagination = PaginationType.PAGINATION;
		pageSize = 20;
		conditionClass = null;
	}
	
	@Override
	protected <E extends Persistable> ExecutableHandler __loader(SearchService<E> searchService, AnnotatedElement aEle) throws Exception {
		Class<?> cc = conditionClass;
		if(null == cc){
			this.name = super.getName();
			if(pagination == PaginationType.NONE){
				cc = SearchCondition.class;
			}else{
				cc = PageCondition.class;
			}
		}else{
			QueryDescriptor queryDescriptor = DescriptorFactory.queryDescriptor(cc);
			this.name = super.getName() + '_' + queryDescriptor.getSimpleName();
			this.conditionObject = queryDescriptor.getAnnotation(ConditionStyle.class);
			for (QueryPropertyAccessor qpd : queryDescriptor.properties()) {
				ConditionContent content = qpd.getAnnotation(ConditionContent.class);
				if(null != content){
					_parseConditionContent(qpd, content);
					continue;
				}
				ConditionTree tree = qpd.getAnnotation(ConditionTree.class);
				if(null != tree){
					String name = tree.name();
					if(StringHelper.isEmpty(name)){
						ResetProperty pr = qpd.getPropertyReseter();
						if(null != pr){
							name = pr.name();
						}
						if(StringHelper.isEmpty(name)){
							name = qpd.queryProperty();
						}
					}
					westBuilder.tree(name, __parseTree(qpd, tree.value()), qpd.getName(), tree.width());
				}
			}
		}
		Class<?> uiClass = uiDescriptor.toClass();
		ItemLoaderHandler handler = new ItemLoaderHandler(searchService, uiClass, cc, aEle);
		_parseExpression(handler, aEle);
		if(!aEle.equals(uiClass)){
			_parseExpression(handler, uiClass);
		}
		if(null != conditionClass && aEle.equals(conditionClass)){
			_parseExpression(handler, conditionClass);
		}
		if(null != queryParams){
			StandardExpressionParser epar = handler.getExpressionParser();
			for(QueryParameter qp : queryParams){
				String name = qp.getName();
				String pn = "x." + name + "=?";
				epar.expression(pn);
				
				epar.value(new RequestExpressionValue(epar, name.replace('.', '_'), qp.getParameterType()));
			}
		}
		List<QueryParameter> qps = handler.getParameters();
		if(!qps.isEmpty()){
			if(null == queryParams){
				queryParams = new ArrayList<QueryParameter>(2);
			}
			queryParams.addAll(qps);
		}
		return handler;
	}
	
	private void _parseExpression(ItemLoaderHandler handler, AnnotatedElement aEle) throws Exception{
		QueryExpression ce = aEle.getAnnotation(QueryExpression.class);
		if(null != ce){
			handler.addExpression(ce);
		}
		
		QueryExpressions ces = aEle.getAnnotation(QueryExpressions.class);
		if(null != ces){
			for(QueryExpression e : ces.value()){
				handler.addExpression(e);
			}
		}
	}
	
//	/**
//	 * 从{@link ConditionExpression}或{@link ConditionExpressions}解析出来的查询表达式
//	 */
//	protected List<QueryParameter> queryParams;
//	private Map<String, Object> extendParameters;
//	
//	@Override
//	public void add(String name, Object value){
//		if(null == extendParameters){
//			extendParameters = new HashMap<String, Object>();
//		}
//		extendParameters.put(name, value);
//	}
	
	@Override
	protected void parseGridAttrs(StringBuilder gattrs) {
//		gattrs.append(",loader:__extendsGridLoader");
	}
	
	@Override
	protected void initGridAttribute(InternalHtmlInfo html, StringBuilder gridAttribute) {

		super.initGridAttribute(html, gridAttribute);
		// 处理其它属性
		boolean customizeView = true;
		gridAttribute.append(", showRownum:true");
		if (pagination == PaginationType.PAGINATION){
//			html.append(" pagination='true' pageSize='").append(pageSize).append('\'');
			gridAttribute.append(", showPagebar:true, pageSize:").append(pageSize);
		}else if(pagination == PaginationType.SCROLLVIEW){
			// 需要引入：datagrid-scrollview.js
			customizeView = false;
			html.includes(html.path("/res/js/plugins/datagrid-scrollview.js"));
//			html.append(" view='scrollview' pageSize='").append(pageSize).append('\'');
			html.append(" data-settings=\"view:'scrollview', pageSize:'").append(pageSize).append("'\"");
		}else{
			gridAttribute.append(", showPagebar:false");
		}
		if(customizeView){
			DetailView detailView = uiDescriptor.getAnnotation(DetailView.class);
			if(null != detailView){
				html.includes(html.path("/res/js/plugins/datagrid-detailview.js"));
				customizeView = false;
				gridAttribute.append(",detailFormatter:function(rowIndex, rowData){return ");
				gridAttribute.append(detailView.value()).append("},view:'detailview'");
			}
		}
		if(customizeView){
			GroupView groupView = uiDescriptor.getAnnotation(GroupView.class);
			if(null != groupView){
				html.includes(html.path("/res/js/plugins/datagrid-groupview.js"));
				customizeView = false;
				gridAttribute.append(",view:'groupview',groupField:'").append(groupView.groupField());
				gridAttribute.append("',groupFormatter:function(value, rows){return ");
				String formatter = groupView.groupFormatter();
				if(formatter.isEmpty()){
					gridAttribute.append("value");
				}else{
					gridAttribute.append(formatter);
				}
				gridAttribute.append(";}");
			}
		}
	}
	
	@Override
	protected String parseToolbar(InternalHtmlInfo html) {
		if(!toolbar.isEmpty()){
			String tid = IdGenerator.fixed();
			html.block("jQuery('#").append(tid).append(" a').bupabutton({flat:true});");
			html.append("<div id='").append(tid).append("'>");
			initToolbar(html);
			html.append("</div>");
			return tid;
		}
		return null;
	}
	
	private void _parseConditionContent(QueryPropertyAccessor qpd, ConditionContent content) throws Exception{
		String propertyName = qpd.getName();
		String name = content.value();
		
		
		
		OptionArray ops = qpd.getAnnotation(OptionArray.class);
		ResetProperty rn = qpd.getPropertyReseter();
		if(null != rn){
			if (StringHelper.isEmpty(name)){
				name = rn.name();
			}
			if(null == ops || ops.value().length == 0){
				ops = rn.options();
			}
		}
		if (StringHelper.isEmpty(name)){
			name = qpd.queryProperty();
		}
		String[] options = null;
		int offset = 0;
		if(null != ops && ops.value().length > 0){
			options = ops.value();
			offset = ops.offset();
		}
		
		int hw = content.headWidth();
		ConditionItem con = new ConditionItem(content.ordinal());
		con.comId = propertyName + id;
		con.comName = propertyName;
		con.item.append("<th align='right'");
		if(hw > 0){
			con.item.append(" style='width:").append(hw).append("px;'");
		}
		int col = content.colspan();
		if (col > 0){
			con.item.append(" colspan='").append((col * 2) + 1).append('\'');
			con.colspan = col;
		}
		con.item.append('>').append(name).append("：</th><td");
		if (content.width() > 0){
			con.item.append(" style='width:").append(content.width()).append("px;'");
		}
		con.item.append('>');
		
		//
		
		EnumSelector es = qpd.getAnnotation(EnumSelector.class);
		if(null != es){
			try{
				Class<?> cls = es.value();
				Method m = cls.getMethod("values");
				Method t = cls.getMethod(es.textProperty());
				Object[] vs = (Object[])m.invoke(null);
				
				//int i = 0;
				con.item.append("<select id='").append(con.comId)
				.append("' name='").append(con.comName).append("'><option value=''>--请选择--</option>");
				for (Object v : vs) {
					con.item.append("<option value='").append(v.toString())
					.append("'>").append((String)t.invoke(v)).append("</option>");
				}
				con.item.append("</select>");
				con.getter();
				
				//for(Object v : vs){
					//__optionNumber(html, bindedName, String.valueOf(i++), (String)t.invoke(v));
					
				//}
			}catch (Exception e) {
				e.printStackTrace();
			}
		}else if (null != options) {
			con.selector(options, offset);
		} else if (qpd.is(boolean.class)) {
			con.checkbox();
		} else if (qpd.is(Boolean.class)) {
			con.selector();
		} else {
			con.input("text");
			ConditionUI cui = content.ui();
			if (cui == ConditionUI.NONE) {
				if (qpd.is(Date.class)) {
					con.component(html.getBlock(), "bupdateinput");
				} else if (qpd.is(int.class, long.class, double.class, float.class, Integer.class, Long.class, Double.class, Float.class)) {
					con.component(html.getBlock(), "bupnuminput");
				} else {
					con.getter();
				}
			} else {
				String cn = cui.getComponent();
				html.block("$('#").append(con.comId).append("').").append(cn).append("({");
				if(cui == ConditionUI.COMBOTREE){
					html.block("url:'").append(__parseTree(qpd, content.value())).append('\'');
				}else if(cui == ConditionUI.COMBOBOX){
					html.block("url:'").append(__parseItem(qpd, content)).append("',valueField:'id'");
				}else if(cui == ConditionUI.COMBOGRID){
					html.block("url:'").append(__parseGrid(qpd, content)).append('\'');
				}
				html.block("});\n");
				con.getter(cn);
			}
		}
		con.item.append("</td>");
		items.add(con);
	}

	private class ConditionItem implements Comparable<ConditionItem> {
		private StringBuilder item = new StringBuilder();
		private QueryParameter param;
		private int colspan = 0;
		private int ordinal;

		/*
		 * 界面组件显示的ID
		 */
		private String comId;
		/*
		 * 界面组件的名称
		 */
		private String comName;
		
		private ConditionItem(int ordinal) {
			this.ordinal = ordinal;
		}

		@Override
		public int compareTo(ConditionItem o) {
			return ordinal - o.ordinal;
		}

		private void getter() {
			param = new QueryParameter(comName, "$('#" + comId + "').val()");
		}

		private void getter(String c) {
			param = new QueryParameter(comName, "$('#" + comId + "')." + c + "('getValue')");
		}
		
		private void component(StringBuilder block, String name){
			block.append("$('#").append(comId).append("').").append(name).append("();");
			getter(name);
		}
		
		private void selector(String[] options, int offset){
			item.append("<select id='").append(comId).append("' name='").append(comName).append("'><option value=''>--请选择--</option>");
			for (int i = 0, len = options.length; i < len; i++) {
				item.append("<option value='").append(i + offset).append("'>").append(options[i]).append("</option>");
			}
			item.append("</select>");
			getter();
		}
		
		private void input(String type){
			item.append("<input name='").append(comName).append("' id='").append(comId).append("' type='").append(type).append("'/>");
		}
		
		private void checkbox(){
			input("checkbox");
			getter();
		}
		
		private void selector(){
			item.append("<select id='").append(comId).append("' name='").append(comName);
			item.append("'><option value=''>--全部--</option>");
			item.append("<option value='true'>是</option>");
			item.append("<option value='false'>否</option>");   
			item.append("</select>");
			getter();
		}
	}

	/**
	 * 在执行了{@link #parse()}之后才能确定该组件的完成的名称
	 */
	@Override
	public String getName() {
		return name;
	}
	
	@Override
	public boolean parse() throws Exception {
		
		// 排序处理
		Collections.sort(items);
		//boolean needLayout = false;
		String lid = null;
		int len = items.size();
		if(len > 0){
			lid = IdGenerator.fixed();
			int c = DEFAULT_COLUMN_SIZE, i = 0;
			if(null != conditionObject && conditionObject.value() > 0){
				c = conditionObject.value();
			}
			html.append("<div data-settings=\"fit:true\" id='").append(lid).append("'><div style='height:");
			html.append(((len - 1) / c + 1) * LINE_HEIGHT + LINE_PADDING);
			html.append("px;padding:3px;margin:0px;background:#fafafa;' data-settings=\"zone:'north',border:false,fit:false\"><table>");
			outer: for (;;) {
				html.append("<tr>");
				for (int j = 0; j < c; j++, i++) {
					if (i >= len) {
						// 补充后面的数量
						for (; j < c; j++) {
							html.append("<td></td><td></td>");
						}
						break;
					}
					ConditionItem item = items.get(i);
					searchParameters.add(item.param);
					html.append(item.item);
					j += item.colspan;
				}
				if (i >= len) {
					String sid = IdGenerator.fixed();
					html.append("<td><a href='javascript:void(null);' id='").append(sid).append("'>搜索</a></td></tr>");
					html.block("jQuery('#").append(sid).append("').bupabutton({iconClassname:'icon_search', flat:true}).click(").append(westBuilder.functionName).append(");");
					break outer;
				} else {
					html.append("<td></td></tr>");
				}
			}
			html.append("</table></div>");
		}
		if(westBuilder.parse() && lid == null){
			lid = IdGenerator.fixed();
			html.append("<div data-settings=\"fit:true\" id='").append(lid).append("'>");
		}
		boolean needLayout = null != lid;
		if(needLayout){
			html.append("<div data-settings=\"zone:'center',border:false, fit:false\">");
			__parse(html); 
			html.append("</div></div>");
			//为了应付非第一次加载完成的情况（如：弹出对话框等），需要在加载完成后再生成layout界面
			html.block("jQuery('#").append(lid).append("').bupborderlayout();");
		}else{
			__parse(html); 
		}
		
		if(null != queryParams){
			NEXT : for(QueryParameter qp : queryParams){
				for(QueryParameter tqp : searchParameters){
					if(qp.check(tqp)){
						continue NEXT;
					}
				}
				searchParameters.add(qp);
			}
		}

		StringBuilder script = _parseDataFunction(html, searchParameters);
		script.append("function ").append(westBuilder.functionName).append("(){jQuery('#").append(id).append("').").append(gridType).append("('reload', ").append(_dataFunction).append("()); return false;};");
		
		return needLayout;
	}
	
	private String __parseGrid(QueryPropertyAccessor qpd, ConditionContent content)throws Exception{
		AutoDataLoader adl = qpd.getAnnotation(AutoDataLoader.class);
		if(null != adl){
			ComboGridAutoLoaderHandler handler = new ComboGridAutoLoaderHandler(manager, adl, qpd.getField());
			manager.register(handler);
			return handler.getUrl();
		}
		return path(content.value());
	}
	
	private String __parseItem(QueryPropertyAccessor qpd, ConditionContent content) throws Exception{
		AutoDataLoader adl = qpd.getAnnotation(AutoDataLoader.class);
		if(null != adl){
			ComboBoxAutoLoaderHandler handler = new ComboBoxAutoLoaderHandler(manager, adl, qpd.getField());
			manager.register(handler);
			return handler.getUrl();
		}
		return path(content.value());
	}
	
	private String __parseTree(QueryPropertyAccessor qpd, String defaultUrl) throws Exception{
		AutoDataLoader adl = qpd.getAnnotation(AutoDataLoader.class);
		if(null != adl){
			ComboAutoLoaderHandler handler;
			if(qpd.isAnnotationPresent(AjaxTreeLoader.class)){
				handler = new ComboTreeAjaxAutoLoaderHandler(manager, adl, qpd.getField());
			}else{
				handler = new ComboTreeAutoLoaderHandler(manager, adl, qpd.getField());
			}
			manager.register(handler);
			return handler.getUrl();
		}
		return path(defaultUrl);
	}
	
	@Override
	public void tree(String url, String id) {
		westBuilder.tree(url, id);
	}
	
	@Override
	public void setWidth(int width) {
		westBuilder.setWidth(width);
	}
//	
	@Override
	public void add(String title, String content, int width, QueryParameter param, String... jss) {
		westBuilder.add(title, content, width, param, jss);
	}
	
	@Override
	public void list(String property, String modelName) {
		westBuilder.list(property, modelName);
	}
	
	public class WestBuilder extends AbstractHtmlBuilder implements SecondaryWorkspace{
		private final static String EMPTY_TITLE = "";
		private WestCondition[] wests = new WestCondition[1];
		private int size = 0;
		private int westWidth = 0;
		private final String functionName = IdGenerator.fixed();
		private final List<QueryParameter> searchParams;
		
		public WestBuilder(InternalHtmlInfo html, List<QueryParameter> searchParams){
			super(html);
			this.searchParams = searchParams;
		}
		
		@Override
		public boolean parse(){
			if(size == 0) return false;
			html.append("<div data-settings=\"zone:'west',collapsible:false,fit:true\" style='width:").append(westWidth).append("px;'");
			
			if(size == 1){
				WestCondition wc = wests[0];
				html.append(" title='").append(wc.title).append("'>").append(wc.content);
			}else{
				String aid = IdGenerator.fixed();
				html.append(" border='false'><div id='").append(aid).append("' data-settings=\"fit:true\">");
				for (int i = 0; i < size; i++) {
					WestCondition wc = wests[i];
					html.append("<div title='").append(wc.title).append("'>").append(wc.content).append("</div>");
				}
				html.append("</div>");
				html.block("jQuery('#").append(aid).append("').bupaccordion({animate:false});");
			}
			html.append("</div>");
			return true;
		}
		
		@Override
		public void setWidth(int width) {
			westWidth = Math.max(westWidth, width);
		}
		
		@Override
		public void add(String title, String content, int width, QueryParameter param, String...jss) {
			if((size + 1) - wests.length > 0){
				wests = Arrays.copyOf(wests, wests.length + 1);
			}
			wests[size++] = new WestCondition(title, content);
			setWidth(width);
			searchParams.add(param);
			if(null != jss){
				for(int i = 0; i < jss.length; i++) html.block(jss[i]);
			}
		}
		
		@Override
		public void tree(String url, String property) {
			tree(EMPTY_TITLE, url, property, DEFAULT_WIDTH);
		}
		
		public void tree(String title, String url, String propertyName, int width){
			String content = "<ul id='" + propertyName + "'></ul>";
			String dt = "jQuery('#" + propertyName + "').buptree('getSelected')";
			QueryParameter param = new ComponenetParameter(propertyName, dt, "id");
			add(title, content, width, param, "jQuery('#", propertyName, "').buptree({url:'", url, "',onSelect:", functionName, "});");
		}
		
		@Override
		public void list(String property, String modelName) {
			list(EMPTY_TITLE, property, modelName, DEFAULT_WIDTH);
		}
		
		public void list(String title, String property, String modelName, int widht){
			String content = "<ul id='" + property + "'>#foreach($li in $" + modelName + ")<li><a href='#'><input value='$li.id' type='hidden'/>$li.name</a></li>#end</ul>";
			QueryParameter param = new QueryParameter(property, "jQuery('#" + property + "').data('save_data_" + property + "')");
			add(title, content, widht, param, "jQuery('#", property, " a').click(function(){jQuery('#", property, "').data('save_data_", property, "',jQuery('input', this).val());", functionName, "();return false;});");
		}
	}
	
	private final List<ConditionItem> items = new ArrayList<ConditionItem>();
	//private final static int DEFAULT_HEAD_WIDTH = 100;
	
	public static class WestCondition{
		final String title;
		final String content;
		public WestCondition(String title, String content){
			this.title = title;
			this.content = content;
		}
	}
}
